﻿#include "pch.h"
#include "OpenGLShader.h"

#include <fstream>
#include <filesystem>
#include <glad/glad.h>
#include "glm/gtc/type_ptr.hpp"

#include "core/Macro.h"
#include "core/log/LogSystem.h"


namespace Engine
{
	static GLenum ShaderTypeFromString(const std::string& type)
	{
		if (type == "vertex")
			return GL_VERTEX_SHADER;
		if (type == "fragment")
			return GL_FRAGMENT_SHADER;

		ENGINE_ASSERT(false, "Unknown shader type!")
		return 0;
	}

	// static shaderc_shader_kind GLShaderStageToShaderC(GLenum stage)
	// {
	// 	switch (stage)
	// 	{
	// 	case GL_VERTEX_SHADER: return shaderc_glsl_vertex_shader;
	// 	case GL_FRAGMENT_SHADER: return shaderc_glsl_fragment_shader;
	// 	}
	// 	ENGINE_ASSERT(false)
	// 	return (shaderc_shader_kind)0;
	// }

	// static const char* GLShaderStageToString(GLenum stage)
	// {
	// 	switch (stage)
	// 	{
	// 	case GL_VERTEX_SHADER: return "GL_VERTEX_SHADER";
	// 	case GL_FRAGMENT_SHADER: return "GL_FRAGMENT_SHADER";
	// 	default:
	// 		ENGINE_ASSERT(false, "")
	// 		return nullptr;
	// 	}
	// }

	static const char* GetCacheDirectory()
	{
		// TODO: make sure the assets directory is valid
		return "assets/shader/OpenGL";
	}

	static void CreateCacheDirectoryIfNeeded()
	{
		if (const std::string cacheDirectory = GetCacheDirectory(); !std::filesystem::exists(cacheDirectory))
			std::filesystem::create_directories(cacheDirectory);
	}

	// static const char* GLShaderStageCachedOpenGLFileExtension(const uint32_t stage)
	// {
	// 	switch (stage)
	// 	{
	// 	case GL_VERTEX_SHADER: return ".cached_opengl.vert";
	// 	case GL_FRAGMENT_SHADER: return ".cached_opengl.frag";
	// 	default:
	// 		ENGINE_ASSERT(false, "")
	// 		return "";
	// 	}
	// }
	//
	// static const char* GLShaderStageCachedVulkanFileExtension(const uint32_t stage)
	// {
	// 	switch (stage)
	// 	{
	// 	case GL_VERTEX_SHADER: return ".cached_vulkan.vert";
	// 	case GL_FRAGMENT_SHADER: return ".cached_vulkan.frag";
	// 	default:
	// 		ENGINE_ASSERT(false, "")
	// 		return "";
	// 	}
	// }

	OpenGLShader::OpenGLShader(const std::string& filePath)
	{
		const std::string shaderSourceCode = ReadFile(filePath);
		std::unordered_map<GLenum, std::string> shaderSource = PreProcess(shaderSourceCode);
		Compile(shaderSource);

		// filePath: Lit.glsl
		// 0: Lit.glsl
		// filePath: assets/shader/OpenGL/Lit.glsl
		// not +1: L/Lit.glsl  +1: /Lit.glsl
		auto lastSlash = filePath.find_last_of("/\\");
		lastSlash = lastSlash == std::string::npos ? 0 : lastSlash + 1;

		const auto lastDot = filePath.rfind('.');

		// filePath: assets/shader/OpenGL/Lit.glsl
		// 名字是 / 到 . 之间的子串 Lit
		const auto count = lastDot == std::string::npos ? filePath.size() - lastSlash : lastDot - lastSlash;
		name = filePath.substr(lastSlash, count);
	}

	OpenGLShader::OpenGLShader(std::string name, const std::string& vertexSrc, const std::string& fragmentSrc)
		: name(std::move(name))
	{
		std::unordered_map<GLenum, std::string> shaderSourceCode;
		shaderSourceCode[GL_VERTEX_SHADER] = vertexSrc;
		shaderSourceCode[GL_FRAGMENT_SHADER] = fragmentSrc;
		Compile(shaderSourceCode);
	}

	OpenGLShader::~OpenGLShader()
	{
		glDeleteProgram(rendererID);
	}

	void OpenGLShader::Bind() const
	{
		glUseProgram(rendererID);
	}

	void OpenGLShader::UnBind() const
	{
		glUseProgram(0);
	}

	void OpenGLShader::SetVec2(const std::string& name, const glm::vec2& value)
	{
		glUniform2fv(glGetUniformLocation(rendererID, name.c_str()), 1, glm::value_ptr(value));
	}

	void OpenGLShader::SetFloat2(const std::string& name, const float x, const float y) const
	{
		glUniform2f(glGetUniformLocation(rendererID, name.c_str()), x, y);
	}

	void OpenGLShader::SetVec3(const std::string& name, const glm::vec3& value)
	{
		glUniform3fv(glGetUniformLocation(rendererID, name.c_str()), 1, glm::value_ptr(value));
	}

	void OpenGLShader::SetFloat3(const std::string& name, const float x, const float y, const float z) const
	{
		glUniform3f(glGetUniformLocation(rendererID, name.c_str()), x, y, z);
	}

	void OpenGLShader::SetVec4(const std::string& name, const glm::vec4& value)
	{
		glUniform4fv(glGetUniformLocation(rendererID, name.c_str()), 1, glm::value_ptr(value));
	}

	void OpenGLShader::SetFloat4(const std::string& name, const float x, const float y, const float z,
	                             const float w) const
	{
		glUniform4f(glGetUniformLocation(rendererID, name.c_str()), x, y, z, w);
	}

	void OpenGLShader::SetUniformInt(const std::string& name, const int value) const
	{
		glUniform1i(glGetUniformLocation(rendererID, name.c_str()), value);
	}

	void OpenGLShader::SetUniformIntArray(const std::string& name, int* values, const unsigned int count)
	{
		glUniform1iv(glGetUniformLocation(rendererID, name.c_str()), static_cast<GLsizei>(count), values);
	}

	void OpenGLShader::SetUniformFloat(const std::string& name, const float value) const
	{
		glUniform1f(glGetUniformLocation(rendererID, name.c_str()), value);
	}

	void OpenGLShader::SetUniformMat2(const std::string& name, const glm::mat2& matrix) const
	{
		glUniformMatrix2fv(glGetUniformLocation(rendererID, name.c_str()), 1, GL_FALSE, glm::value_ptr(matrix));
	}

	void OpenGLShader::SetUniformMat3(const std::string& name, const glm::mat3& matrix) const
	{
		glUniformMatrix3fv(glGetUniformLocation(rendererID, name.c_str()), 1, GL_FALSE, glm::value_ptr(matrix));
	}

	void OpenGLShader::SetUniformMat4(const std::string& name, const glm::mat4& matrix) const
	{
		glUniformMatrix4fv(glGetUniformLocation(rendererID, name.c_str()), 1, GL_FALSE, glm::value_ptr(matrix));
	}

	std::string OpenGLShader::ReadFile(const std::string& filePath)
	{
		std::string result;
		if (std::ifstream in(filePath, std::ios::in | std::ios::binary); in)
		{
			// 移动到文件末尾
			in.seekg(0, std::ios::end);
			// tellg()当前索引，即获取文件的二进制长度，按照字节来计算偏移
			// string.resize()按照char为单位，也是字节
			result.resize(in.tellg());
			// 移动到文件开头
			in.seekg(0, std::ios::beg);
			// 读取文件内容到result
			// 注意! &result[0]获取的是真正放字符串内容的内存地址，和std::string的地址是不一样的
			in.read(result.data(), static_cast<long long>(result.size()));
			in.close();

			// Remove BOM if present
			if (result.size() >= 3 && static_cast<unsigned char>(result[0]) == 0xEF &&
				static_cast<unsigned char>(result[1]) == 0xBB &&
				static_cast<unsigned char>(result[2]) == 0xBF)
			{
				result.erase(0, 3);
			}
		}
		else
		{
			ENGINE_ERROR("无法打开文件：'{0}'", filePath)
		}

		return result;
	}

	std::unordered_map<GLenum, std::string> OpenGLShader::PreProcess(const std::string& source)
	{
		std::unordered_map<GLenum, std::string> shaderSource;

		const auto typeToken = "#type";
		const size_t typeTokenLength = strlen(typeToken);

		// pos： 指向"#type"开头
		// eol：指向"vertex"末尾
		// begin：指向"vertex"开头
		// nextLinePos：指向"#version"开头
		size_t pos = source.find(typeToken, 0);
		while (pos != std::string::npos)
		{
			const size_t eol = source.find_first_of("\r\n", pos); // #type后面的换行
			ENGINE_ASSERT(eol != std::string::npos, "Syntax error") // 没有换行，则后面没有逻辑，语法错误
			const size_t begin = pos + typeTokenLength + 1; // glsl第一行代码 +1表示跳过一个空格
			std::string type = source.substr(begin, eol - begin);
			ENGINE_ASSERT(ShaderTypeFromString(type), "Invalid shader type specified!")

			const size_t nextLinePos = source.find_first_not_of("\r\n", eol);
			pos = source.find(typeToken, nextLinePos); // 找到下一个"#type"
			// shaderSource[ShaderTypeFromString(type)] = source.substr(nextLinePos, pos - nextLinePos);
			shaderSource[ShaderTypeFromString(type)] = source.substr(nextLinePos,
			                                                         pos - (nextLinePos == std::string::npos
				                                                                ? source.size() - 1
				                                                                : nextLinePos));
		}
		return shaderSource;
	}

	void OpenGLShader::Compile(std::unordered_map<GLenum, std::string>& shaderSourceCode)
	{
		const GLuint program = glCreateProgram();
		ENGINE_ASSERT(shaderSourceCode.size() <= 2, "现在只支持两个shader")
		std::array<GLenum, 2> glShaderIDs;
		int index = 0;
		for (auto& [type, source] : shaderSourceCode)
		{
			// GLenum type = type;
			// const std::string& source = source;
			GLuint shader = glCreateShader(type);
			const GLchar* sourceCStr = source.c_str();
			glShaderSource(shader, 1, &sourceCStr, nullptr);
			glCompileShader(shader);

			GLint isCompiled = 0;
			glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
			if (isCompiled == GL_FALSE)
			{
				GLint maxLength = 0;
				glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
				std::vector<GLchar> infoLog(maxLength);
				glGetShaderInfoLog(shader, maxLength, &maxLength, infoLog.data());
				glDeleteShader(shader);

				ENGINE_ERROR("{0}", infoLog.data())
				ENGINE_ASSERT(false, "OpenGLShader 编译错误！")
				break;
			}

			glAttachShader(program, shader);
			glShaderIDs[index++] = shader;
		}

		rendererID = program;
		glLinkProgram(program);
		GLint isLinked = 0;
		glGetProgramiv(program, GL_LINK_STATUS, (int*)&isLinked);
		if (isLinked == GL_FALSE)
		{
			GLint maxLength = 0;
			glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);
			std::vector<GLchar> infoLog(maxLength);
			glGetProgramInfoLog(program, maxLength, &maxLength, infoLog.data());
			glDeleteProgram(program);
			for (const auto id : glShaderIDs)
			{
				glDeleteShader(id);
			}
			ENGINE_ERROR("{0}", infoLog.data())
			ENGINE_ASSERT(false, "OpenGLShader 链接错误！")
			return;
		}
		for (const auto id : glShaderIDs)
		{
			glDetachShader(program, id);
		}
	}
}
